package org.fxml {
	import org.fxml.commands.Command;
	import org.fxml.constants.Keywords;
	import org.fxml.events.ApplicationEvent;
	import org.fxml.helpers.ClassBuilderFactory;
	import org.fxml.loaders.BulkLoader;
	import org.fxml.managers.AssetManager;
	import org.fxml.managers.CommandManager;
	import org.fxml.utils.BooleanUtil;
	import org.fxml.utils.DisplayUtil;
	import org.fxml.utils.FlashVarUtil;
	import org.fxml.utils.Version;

	import mx.events.FlexEvent;
	import mx.utils.NameUtil;

	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.Dictionary;

	/**
	 * @author jordandoczy
	 * @Main class from which the Application is built
	 */
	public class Application extends Sprite implements IObject {
		
		protected var _debugOutput:flash.text.TextField;
		
		protected var _childIndex:uint = 0;
		protected var _classBuilderFactory:ClassBuilderFactory;
		protected var _configXML:XML;
		protected var _commandManager:CommandManager = new CommandManager();
		protected var _properties:Dictionary = new Dictionary();
		protected var _urlLoader:URLLoader = new URLLoader();
		protected var _version:Version = new Version(1,1,0,8);
		protected var _waitingForEvent : Boolean;
		
		BulkLoader;
		AssetManager;
		

		/**
		 * @public
		 * constructor 
		 */				
		public function Application(){
			addEventListener( Event.ADDED_TO_STAGE, _init, false, 1, true);
			_classBuilderFactory = new ClassBuilderFactory(this);
			_classBuilderFactory.addEventListener(ApplicationEvent.EVENT_ADDED, onEventAdded, false, 0, true);
		}

		
		/**
		 * @public
		 * @return Version
		 */
		public function get version():Version{
			return _version;
		}
		
		/*
		 * @internal
		 */
		internal function get request():URLRequest{
			return new URLRequest(FlashVarUtil.getValue(this, Keywords.CONFIG_FILE));
		}
		
		
		/*
		 * @public
		 * @param name The name of the property
		 * @return The property of type * or null 
		 */
		public function getProperty(name:String):*{
			if(_properties[name]) return _properties[name];
			else{
				try{
					return getChildByName(name) || this[name];
				}
				catch(e:Error) {}
			}
			return null;
		}
		
		/*
		 * @public
		 * @param name The name of the property
		 * @return true if the property exists, otherwise false
		 */
		public function hasProperty(name:String):Boolean{
			if(_properties[name]) return true;
			else{
				try{
					return contains(getChildByName(name));
				}
				catch(e:Error) {}
			}
			return false;
		}
		
		
		/*
		 * @public
		 * @param name The name of the property
		 * 
		 * @param value The value to assign to the property
		 */
		public function setProperty(name:String, value:*):void{
			if(_properties[name] != value){
				_properties[name] = value;
			}
		}
		
		
		/*
		 * @protected
		 * Adds a textfield over top of the display list to show trace 
		 * statements
		 */
		protected function addDebugOutput():void{
			if(_configXML.debug && BooleanUtil(_configXML.debug)){ 
				_debugOutput = new TextField();
				_debugOutput.name = Keywords.DEBUG;
				_debugOutput.width = 400;
				_debugOutput.autoSize = TextFieldAutoSize.LEFT;
				_debugOutput.background = true;
				_debugOutput.border = true;
				addChild(_debugOutput);
				
				//Logger.ALL = true;
				//Logger.addEventListener(LogEvent.LOG, onLog, false, 0, true);
				//Logger.debug(version);
				//Logger.debug(FlashVarUtil.getValue(this, Keywords.CONFIG_FILE));
			}
		}
		
		/*
		 * @internal
		 * Replaces all occurances of "flashvars.{string}" where {string} is 
		 * the name of the flash var passed through
		 * @example flashvars.id
		 */
		internal function adjustXML():void{
			var value:String = _configXML.toString();
			var reg:RegExp = /flashvars\.([A-Za-z0-9_]+)/i;
			var matches:Object;
			var result:String;
			var variable:String;
			
			try{
				
				while (matches = reg.exec(value)){ // while not equal to null
					try{
						variable = matches[1];
						result = FlashVarUtil.getValue(this, variable);
						value = value.replace(matches[0], result);
						matches = reg.exec(value);
					}
					catch(e:Error) {}
				} 
				
				_configXML = new XML(value);
			}
			catch(e:Error){}
		}
		
		/*
		 * @internal
		 * Dispatches an Event.INIT when all objects have been parsed
		 */
		internal function complete():void{
			dispatchEvent(new Event(Event.INIT));
		}
		
		/*
		 * @internal
		 * Defines the program flow 
		 */
		internal function _init(event:Event):void{
			removeEventListener( Event.ADDED_TO_STAGE, _init);
			
			_commandManager.addCommand(new Command(_urlLoader.load, _urlLoader, Event.COMPLETE, [request]));
			_commandManager.addCommand(new Command(setXML));
			_commandManager.addCommand(new Command(adjustXML));
			_commandManager.addCommand(new Command(addDebugOutput));
			_commandManager.addCommand(new Command(parseXML, this, FlexEvent.CREATION_COMPLETE));
			_commandManager.addCommand(new Command(complete));
			_commandManager.trigger();
		}
		
		/*
		 * @internal
		 * Parses the XML and creates objects from each child node
		 */
		internal function parseXML():void{
			
			var item:XML;
			var items:XMLList;
			var name:String;
			var object:*;
			
			try{
				items = _configXML.children();
				while(_childIndex < items.length()){
					
					if(_waitingForEvent) break;
					
					item = items[_childIndex];
					name = item.localName();
					
					if(item.localName() && item.localName() != Keywords.DEBUG){ // skip import,debug
						object = _classBuilderFactory.createInstance(item);
						
						if(object is DisplayObject && item.localName() == Keywords.CHILD){
							addChild(object as DisplayObject);
							name = NameUtil.displayObjectToString(object as DisplayObject);
						}
						
						setProperty(name, object);
					}
					_childIndex++;
				}
			}
			catch(e:Error){
				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
			}
			
			if(_childIndex == items.length()){
				dispatchEvent(new Event(FlexEvent.CREATION_COMPLETE));
				if(getChildByName(Keywords.DEBUG) != null) DisplayUtil.bringToFront(getChildByName(Keywords.DEBUG));
			}
			
		}
		
		/*
		 * @internal
		 * sets the _configXML
		 */
		internal function setXML():void{
			_configXML = XML(_urlLoader.data);
		}
		
		protected function onEventAdded(event : ApplicationEvent) : void {
			event.eventTarget.addEventListener(event.event, onEventComplete, false, 0, true);
			_waitingForEvent = true;
		}
		
		protected function onEventComplete(event:Event):void{
			try{
				event.target.removeEventListener(event, onEventComplete, false);
			}
			catch(e:Error) {}
			
			_waitingForEvent = false;
			parseXML();
		}
		

		/*
		 * @protected
		 * Dispatches an ErrorEvent
		 */
		protected function onLoadingError(event:IOErrorEvent):void{
			dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, event.text));
		}

		/*
		 * @protected 
		 * Writes to the debug output
		 */
		/*protected function onLog(event:LogEvent):void{
			try{
				if(contains(_debugOutput)){
					_debugOutput.appendText(event.message + "\n");
				}
			}
			catch(e:Error){}
		}*/
		
		/*
		 * @protected
		 * Dispatches a ProgressEvent
		 */
		protected function onProgress(event:ProgressEvent):void{
			dispatchEvent(event);
		}
	}
}